// Copyright Epic Games, Inc. All Rights Reserved. // 版权声明，Epic Games 公司保留所有权利

#include "LyraGameplayCueManager.h" // 包含 LyraGameplayCueManager 的头文件
#include "Engine/AssetManager.h" // 包含 AssetManager 的头文件，用于资源管理
#include "LyraLogChannels.h" // 包含 Lyra 特定的日志频道头文件
#include "GameplayCueSet.h" // 包含 GameplayCueSet 的头文件
#include "AbilitySystemGlobals.h" // 包含 AbilitySystemGlobals 的头文件，用于获取全局能力系统实例
#include "GameplayTagsManager.h" // 包含 GameplayTagsManager 的头文件，用于管理 GameplayTag
#include "UObject/UObjectThreadContext.h" // 包含 UObjectThreadContext 的头文件，用于获取序列化上下文
#include "Async/Async.h" // 包含 Async 的头文件，用于异步任务

#include UE_INLINE_GENERATED_CPP_BY_NAME(LyraGameplayCueManager) // 包含由 UHT 生成的内联函数和属性定义

////////////////////////////////////////////////////////////////////// // 分隔符

enum class ELyraEditorLoadMode // 枚举类，定义 Lyra 编辑器的加载模式
{
	// Loads all cues upfront; longer loading speed in the editor but short PIE times and effects never fail to play // 注释，模式1：预先加载所有 Cues；编辑器内加载时间较长，但 PIE 时间短，效果从不播放失败
	LoadUpfront, // 枚举值：预先加载

	// Outside of editor: Async loads as cue tag are registered // 注释，模式2：在编辑器外，当 Cue Tag 被注册时异步加载
	// In editor: Async loads when cues are invoked // 注释，模式2：在编辑器内，当 Cues 被调用时异步加载
	//   Note: This can cause some 'why didn't I see the effect for X' issues in PIE and is good for iteration speed but otherwise bad for designers // 注释，模式2：注意：这可能导致在 PIE 中出现“为什么我没有看到 X 效果”的问题，有利于迭代速度，但不利于设计师
	PreloadAsCuesAreReferenced_GameOnly, // 枚举值：仅在游戏中当 Cues 被引用时预加载

	// Async loads as cue tag are registered // 注释，模式3：当 Cue Tag 被注册时异步加载
	PreloadAsCuesAreReferenced // 枚举值：当 Cues 被引用时预加载
};

namespace LyraGameplayCueManagerCvars // 命名空间，用于包含控制台变量和命令
{
	static FAutoConsoleCommand CVarDumpGameplayCues( // 静态控制台命令变量，用于打印 GameplayCues
		TEXT("Lyra.DumpGameplayCues"), // 命令字符串
		TEXT("Shows all assets that were loaded via LyraGameplayCueManager and are currently in memory."), // 命令帮助文本
		FConsoleCommandWithArgsDelegate::CreateStatic(ULyraGameplayCueManager::DumpGameplayCues)); // 将命令委托绑定到静态函数 ULyraGameplayCueManager::DumpGameplayCues

	static ELyraEditorLoadMode LoadMode = ELyraEditorLoadMode::LoadUpfront; // 静态枚举变量，定义当前的加载模式，默认值为 LoadUpfront
}

const bool bPreloadEvenInEditor = true; // 常量布尔值，定义是否即使在编辑器中也进行预加载，初始化为 true

////////////////////////////////////////////////////////////////////// // 分隔符

struct FGameplayCueTagThreadSynchronizeGraphTask : public FAsyncGraphTaskBase // 定义结构体 FGameplayCueTagThreadSynchronizeGraphTask，继承自 FAsyncGraphTaskBase，用于在线程间同步任务
{
	TFunction<void()> TheTask; // TFunction 成员变量，存储要执行的任务
	FGameplayCueTagThreadSynchronizeGraphTask(TFunction<void()>&& Task) : TheTask(MoveTemp(Task)) { } // 构造函数，接受一个右值引用 TFunction 并移动赋值给 TheTask
	void DoTask(ENamedThreads::Type CurrentThread, const FGraphEventRef& MyCompletionGraphEvent) { TheTask(); } // 重写 DoTask 函数，在指定线程执行 TheTask
	ENamedThreads::Type GetDesiredThread() { return ENamedThreads::GameThread; } // 重写 GetDesiredThread 函数，指定任务希望在游戏线程执行
};

////////////////////////////////////////////////////////////////////// // 分隔符

ULyraGameplayCueManager::ULyraGameplayCueManager(const FObjectInitializer& ObjectInitializer) // 构造函数实现
	: Super(ObjectInitializer) // 调用父类 UGameplayCueManager 的构造函数
{
}

ULyraGameplayCueManager* ULyraGameplayCueManager::Get() // 静态成员函数 Get 的实现
{
	return Cast<ULyraGameplayCueManager>(UAbilitySystemGlobals::Get().GetGameplayCueManager()); // 通过 UAbilitySystemGlobals 获取 GameplayCueManager 并尝试转换为 ULyraGameplayCueManager
}

void ULyraGameplayCueManager::OnCreated() // OnCreated 函数的实现
{
	Super::OnCreated(); // 调用父类的 OnCreated 函数

	UpdateDelayLoadDelegateListeners(); // 调用 UpdateDelayLoadDelegateListeners 来更新延迟加载委托的监听器
}

void ULyraGameplayCueManager::LoadAlwaysLoadedCues() // LoadAlwaysLoadedCues 函数的实现
{
	if (ShouldDelayLoadGameplayCues()) // 检查是否应该延迟加载 GameplayCues
	{
		UGameplayTagsManager& TagManager = UGameplayTagsManager::Get(); // 获取 GameplayTagsManager 的单例引用
	
		//@TODO: Try to collect these by filtering GameplayCue. tags out of native gameplay tags? // TODO 注释：尝试通过从原生 GameplayTags 中过滤出 GameplayCue. 标签来收集这些？
		TArray<FName> AdditionalAlwaysLoadedCueTags; // 定义 FName 数组，存储额外的始终加载的 Cue Tags（目前为空）

		for (const FName& CueTagName : AdditionalAlwaysLoadedCueTags) // 遍历 AdditionalAlwaysLoadedCueTags 数组
		{
			FGameplayTag CueTag = TagManager.RequestGameplayTag(CueTagName, /*ErrorIfNotFound=*/ false); // 请求 GameplayTag，如果未找到不报错
			if (CueTag.IsValid()) // 检查获取到的 Tag 是否有效
			{
				ProcessTagToPreload(CueTag, nullptr); // 调用 ProcessTagToPreload 处理该 Tag，所有者对象为 nullptr 表示始终加载
			}
			else // 如果 Tag 无效
			{
				UE_LOG(LogLyra, Warning, TEXT("ULyraGameplayCueManager::AdditionalAlwaysLoadedCueTags contains invalid tag %s"), *CueTagName.ToString()); // 输出警告日志，指出 AdditionalAlwaysLoadedCueTags 包含了无效的 Tag
			}
		}
	}
}

bool ULyraGameplayCueManager::ShouldAsyncLoadRuntimeObjectLibraries() const // ShouldAsyncLoadRuntimeObjectLibraries 函数的实现
{
	switch (LyraGameplayCueManagerCvars::LoadMode) // 根据 LoadMode 的值进行切换
	{
	case ELyraEditorLoadMode::LoadUpfront: // 如果是 LoadUpfront 模式
		return true; // 返回 true，表示应该异步加载运行时对象库
	case ELyraEditorLoadMode::PreloadAsCuesAreReferenced_GameOnly: // 如果是 PreloadAsCuesAreReferenced_GameOnly 模式
#if WITH_EDITOR // 如果有编辑器宏定义
		if (GIsEditor) // 如果当前运行在编辑器中
		{
			return false; // 返回 false，表示在编辑器中不异步加载
		}
#endif
		break; // 跳出 switch
	case ELyraEditorLoadMode::PreloadAsCuesAreReferenced: // 如果是 PreloadAsCuesAreReferenced 模式
		break; // 跳出 switch
	}

	return !ShouldDelayLoadGameplayCues(); // 返回是否不延迟加载 GameplayCues 的逻辑结果
}

bool ULyraGameplayCueManager::ShouldSyncLoadMissingGameplayCues() const // ShouldSyncLoadMissingGameplayCues 函数的实现
{
	return false; // 返回 false，表示不应同步加载缺失的 GameplayCues
}

bool ULyraGameplayCueManager::ShouldAsyncLoadMissingGameplayCues() const // ShouldAsyncLoadMissingGameplayCues 函数的实现
{
	return true; // 返回 true，表示应异步加载缺失的 GameplayCues
}

void ULyraGameplayCueManager::DumpGameplayCues(const TArray<FString>& Args) // 静态成员函数 DumpGameplayCues 的实现
{
	ULyraGameplayCueManager* GCM = Cast<ULyraGameplayCueManager>(UAbilitySystemGlobals::Get().GetGameplayCueManager()); // 获取 ULyraGameplayCueManager 实例
	if (!GCM) // 如果获取失败
	{
		UE_LOG(LogLyra, Error, TEXT("DumpGameplayCues failed. No ULyraGameplayCueManager found.")); // 输出错误日志
		return; // 返回
	}

	const bool bIncludeRefs = Args.Contains(TEXT("Refs")); // 检查命令行参数是否包含 "Refs" 字符串，用于决定是否打印引用信息

	UE_LOG(LogLyra, Log, TEXT("=========== Dumping Always Loaded Gameplay Cue Notifies ===========")); // 输出日志，开始打印始终加载的 GameplayCueNotifies
	for (UClass* CueClass : GCM->AlwaysLoadedCues) // 遍历 AlwaysLoadedCues 集合
	{
		UE_LOG(LogLyra, Log, TEXT("  %s"), *GetPathNameSafe(CueClass)); // 输出每个始终加载的 Cue 类的路径名
	}

	UE_LOG(LogLyra, Log, TEXT("=========== Dumping Preloaded Gameplay Cue Notifies ===========")); // 输出日志，开始打印预加载的 GameplayCueNotifies
	for (UClass* CueClass : GCM->PreloadedCues) // 遍历 PreloadedCues 集合
	{
		TSet<FObjectKey>* ReferencerSet = GCM->PreloadedCueReferencers.Find(CueClass); // 在 PreloadedCueReferencers Map 中查找该 CueClass 对应的引用者集合
		int32 NumRefs = ReferencerSet ? ReferencerSet->Num() : 0; // 计算引用数量，如果找到集合则取其大小，否则为 0
		UE_LOG(LogLyra, Log, TEXT("  %s (%d refs)"), *GetPathNameSafe(CueClass), NumRefs); // 输出每个预加载的 Cue 类的路径名和引用数量
		if (bIncludeRefs && ReferencerSet) // 如果需要包含引用信息且找到了引用者集合
		{
			for (const FObjectKey& Ref : *ReferencerSet) // 遍历引用者集合
			{
				UObject* RefObject = Ref.ResolveObjectPtr(); // 将 FObjectKey 解析为 UObject 指针
				UE_LOG(LogLyra, Log, TEXT("    ^- %s"), *GetPathNameSafe(RefObject)); // 输出每个引用者的路径名
			}
		}
	}

	UE_LOG(LogLyra, Log, TEXT("=========== Dumping Gameplay Cue Notifies loaded on demand ===========")); // 输出日志，开始打印按需加载的 GameplayCueNotifies
	int32 NumMissingCuesLoaded = 0; // 计数器，初始化为 0，用于统计按需加载的 Cues 数量
	if (GCM->RuntimeGameplayCueObjectLibrary.CueSet) // 检查 RuntimeGameplayCueObjectLibrary 的 CueSet 是否存在
	{
		for (const FGameplayCueNotifyData& CueData : GCM->RuntimeGameplayCueObjectLibrary.CueSet->GameplayCueData) // 遍历 CueSet 中的所有 GameplayCueNotifyData
		{
			if (CueData.LoadedGameplayCueClass && !GCM->AlwaysLoadedCues.Contains(CueData.LoadedGameplayCueClass) && !GCM->PreloadedCues.Contains(CueData.LoadedGameplayCueClass)) // 如果 Cue 类已加载，且不在 AlwaysLoadedCues 也不在 PreloadedCues 中
			{
				NumMissingCuesLoaded++; // 计数器增加
				UE_LOG(LogLyra, Log, TEXT("  %s"), *CueData.LoadedGameplayCueClass->GetPathName()); // 输出该按需加载的 Cue 类的路径名
			}
		}
	}

	UE_LOG(LogLyra, Log, TEXT("=========== Gameplay Cue Notify summary ===========")); // 输出日志，开始打印 GameplayCueNotify 摘要
	UE_LOG(LogLyra, Log, TEXT("  ... %d cues in always loaded list"), GCM->AlwaysLoadedCues.Num()); // 输出始终加载列表中的 Cues 数量
	UE_LOG(LogLyra, Log, TEXT("  ... %d cues in preloaded list"), GCM->PreloadedCues.Num()); // 输出预加载列表中的 Cues 数量
	UE_LOG(LogLyra, Log, TEXT("  ... %d cues loaded on demand"), NumMissingCuesLoaded); // 输出按需加载的 Cues 数量
	UE_LOG(LogLyra, Log, TEXT("  ... %d cues in total"), GCM->AlwaysLoadedCues.Num() + GCM->PreloadedCues.Num() + NumMissingCuesLoaded); // 输出 Cues 总数
}

void ULyraGameplayCueManager::OnGameplayTagLoaded(const FGameplayTag& Tag) // OnGameplayTagLoaded 函数的实现
{
	FScopeLock ScopeLock(&LoadedGameplayTagsToProcessCS); // 获取 LoadedGameplayTagsToProcessCS 临界区的锁，防止多线程竞争
	bool bStartTask = LoadedGameplayTagsToProcess.Num() == 0; // 判断是否是第一个要处理的 Tag，如果是则需要启动处理任务
	FUObjectSerializeContext* LoadContext = FUObjectThreadContext::Get().GetSerializeContext(); // 获取当前 UObject 线程的序列化上下文
	UObject* OwningObject = LoadContext ? LoadContext->SerializedObject : nullptr; // 从序列化上下文中获取正在序列化的对象作为所有者对象，如果没有则为 nullptr
	LoadedGameplayTagsToProcess.Emplace(Tag, OwningObject); // 将 Tag 和所有者对象的信息添加到 LoadedGameplayTagsToProcess 数组中
	if (bStartTask) // 如果是第一个 Tag，需要启动任务
	{
		TGraphTask<FGameplayCueTagThreadSynchronizeGraphTask>::CreateTask().ConstructAndDispatchWhenReady([]() // 创建并分发一个 FGameplayCueTagThreadSynchronizeGraphTask 图任务到游戏线程
			{
				if (GIsRunning) // 检查引擎是否正在运行
				{
					if (ULyraGameplayCueManager* StrongThis = Get()) // 获取 ULyraGameplayCueManager 实例
					{
						// If we are garbage collecting we cannot call StaticFindObject (or a few other static uobject functions), so we'll just wait until the GC is over and process the tags then // 注释：如果正在垃圾回收，我们不能调用 StaticFindObject（或其他一些静态 UObject 函数），所以我们将等待 GC 结束后再处理 Tags
						if (IsGarbageCollecting()) // 检查是否正在垃圾回收
						{
							StrongThis->bProcessLoadedTagsAfterGC = true; // 设置标志位，表示需要在 GC 后处理 Tags
						}
						else // 如果不在垃圾回收
						{
							StrongThis->ProcessLoadedTags(); // 直接调用 ProcessLoadedTags 处理 Tags
						}
					}
				}
			});
	}
}

void ULyraGameplayCueManager::HandlePostGarbageCollect() // HandlePostGarbageCollect 函数的实现
{
	if (bProcessLoadedTagsAfterGC) // 检查是否需要在 GC 后处理 Tags
	{
		ProcessLoadedTags(); // 调用 ProcessLoadedTags 处理 Tags
	}
	bProcessLoadedTagsAfterGC = false; // 重置标志位为 false
}

void ULyraGameplayCueManager::ProcessLoadedTags() // ProcessLoadedTags 函数的实现
{
	TArray<FLoadedGameplayTagToProcessData> TaskLoadedGameplayTagsToProcess; // 定义局部数组，用于存储从共享数组复制过来的待处理数据
	{
		// Lock LoadedGameplayTagsToProcess just long enough to make a copy and clear // 注释：锁定 LoadedGameplayTagsToProcess 仅足够长的时间来复制和清空
		FScopeLock TaskScopeLock(&LoadedGameplayTagsToProcessCS); // 获取 LoadedGameplayTagsToProcessCS 临界区的锁
		TaskLoadedGameplayTagsToProcess = LoadedGameplayTagsToProcess; // 将 LoadedGameplayTagsToProcess 的内容复制到局部数组
		LoadedGameplayTagsToProcess.Empty(); // 清空共享数组
	}

	// This might return during shutdown, and we don't want to proceed if that is the case // 注释：这可能在关闭期间返回，如果是这种情况我们不希望继续
	if (GIsRunning) // 检查引擎是否正在运行
	{
		if (RuntimeGameplayCueObjectLibrary.CueSet) // 检查 RuntimeGameplayCueObjectLibrary 的 CueSet 是否存在
		{
			for (const FLoadedGameplayTagToProcessData& LoadedTagData : TaskLoadedGameplayTagsToProcess) // 遍历局部数组中的每个待处理数据
			{
				if (RuntimeGameplayCueObjectLibrary.CueSet->GameplayCueDataMap.Contains(LoadedTagData.Tag)) // 检查 CueSet 的 GameplayCueDataMap 是否包含该 Tag
				{
					if (!LoadedTagData.WeakOwner.IsStale()) // 检查弱引用的所有者对象是否仍然有效（未被垃圾回收）
					{
						ProcessTagToPreload(LoadedTagData.Tag, LoadedTagData.WeakOwner.Get()); // 调用 ProcessTagToPreload 处理该 Tag 和所有者对象
					}
				}
			}
		}
		else // 如果 CueSet 为 nullptr
		{
			UE_LOG(LogLyra, Warning, TEXT("ULyraGameplayCueManager::OnGameplayTagLoaded processed loaded tag(s) but RuntimeGameplayCueObjectLibrary.CueSet was null. Skipping processing.")); // 输出警告日志
		}
	}
}

void ULyraGameplayCueManager::ProcessTagToPreload(const FGameplayTag& Tag, UObject* OwningObject) // ProcessTagToPreload 函数的实现
{
	switch (LyraGameplayCueManagerCvars::LoadMode) // 根据 LoadMode 的值进行切换
	{
	case ELyraEditorLoadMode::LoadUpfront: // 如果是 LoadUpfront 模式
		return; // 直接返回，不进行预加载
	case ELyraEditorLoadMode::PreloadAsCuesAreReferenced_GameOnly: // 如果是 PreloadAsCuesAreReferenced_GameOnly 模式
#if WITH_EDITOR // 如果有编辑器宏定义
		if (GIsEditor) // 如果当前运行在编辑器中
		{
			return; // 直接返回，在编辑器中不进行预加载
		}
#endif
		break; // 跳出 switch
	case ELyraEditorLoadMode::PreloadAsCuesAreReferenced: // 如果是 PreloadAsCuesAreReferenced 模式
		break; // 跳出 switch
	}

	check(RuntimeGameplayCueObjectLibrary.CueSet); // 使用 check 宏确保 RuntimeGameplayCueObjectLibrary.CueSet 不为 nullptr，如果为 nullptr 则断言失败

	int32* DataIdx = RuntimeGameplayCueObjectLibrary.CueSet->GameplayCueDataMap.Find(Tag); // 在 GameplayCueDataMap 中查找 Tag 对应的数据索引
	if (DataIdx && RuntimeGameplayCueObjectLibrary.CueSet->GameplayCueData.IsValidIndex(*DataIdx)) // 如果找到了索引且索引在 GameplayCueData 数组范围内
	{
		const FGameplayCueNotifyData& CueData = RuntimeGameplayCueObjectLibrary.CueSet->GameplayCueData[*DataIdx]; // 获取对应的 FGameplayCueNotifyData

		UClass* LoadedGameplayCueClass = FindObject<UClass>(nullptr, *CueData.GameplayCueNotifyObj.ToString()); // 尝试根据 CueData 中的路径字符串查找已加载的 UClass
		if (LoadedGameplayCueClass) // 如果 Cue 类已经加载
		{
			RegisterPreloadedCue(LoadedGameplayCueClass, OwningObject); // 直接调用 RegisterPreloadedCue 注册预加载的 Cue
		}
		else // 如果 Cue 类尚未加载
		{
			bool bAlwaysLoadedCue = OwningObject == nullptr; // 判断是否为始终加载的 Cue（所有者对象为 nullptr）
			TWeakObjectPtr<UObject> WeakOwner = OwningObject; // 创建所有者对象的弱引用
			StreamableManager.RequestAsyncLoad(CueData.GameplayCueNotifyObj, FStreamableDelegate::CreateUObject(this, &ThisClass::OnPreloadCueComplete, CueData.GameplayCueNotifyObj, WeakOwner, bAlwaysLoadedCue), FStreamableManager::DefaultAsyncLoadPriority, false, false, TEXT("GameplayCueManager")); // 使用 StreamableManager 异步加载 Cue 类对象，加载完成后调用 OnPreloadCueComplete
		}
	}
}

void ULyraGameplayCueManager::OnPreloadCueComplete(FSoftObjectPath Path, TWeakObjectPtr<UObject> OwningObject, bool bAlwaysLoadedCue) // OnPreloadCueComplete 函数的实现
{
	if (bAlwaysLoadedCue || OwningObject.IsValid()) // 检查是否是始终加载的 Cue 或者所有者对象仍然有效
	{
		if (UClass* LoadedGameplayCueClass = Cast<UClass>(Path.ResolveObject())) // 解析路径获取 UObject 并尝试转换为 UClass
		{
			RegisterPreloadedCue(LoadedGameplayCueClass, OwningObject.Get()); // 调用 RegisterPreloadedCue 注册预加载的 Cue
		}
	}
}

void ULyraGameplayCueManager::RegisterPreloadedCue(UClass* LoadedGameplayCueClass, UObject* OwningObject) // RegisterPreloadedCue 函数的实现
{
	check(LoadedGameplayCueClass); // 使用 check 宏确保 LoadedGameplayCueClass 不为 nullptr

	const bool bAlwaysLoadedCue = OwningObject == nullptr; // 判断是否为始终加载的 Cue（所有者对象为 nullptr）
	if (bAlwaysLoadedCue) // 如果是始终加载的 Cue
	{
		AlwaysLoadedCues.Add(LoadedGameplayCueClass); // 添加到 AlwaysLoadedCues 集合
		PreloadedCues.Remove(LoadedGameplayCueClass); // 从 PreloadedCues 集合中移除（如果存在）
		PreloadedCueReferencers.Remove(LoadedGameplayCueClass); // 从 PreloadedCueReferencers Map 中移除（如果存在）
	}
	else if ((OwningObject != LoadedGameplayCueClass) && (OwningObject != LoadedGameplayCueClass->GetDefaultObject()) && !AlwaysLoadedCues.Contains(LoadedGameplayCueClass)) // 如果所有者对象不是 Cue 类本身，也不是 Cue 类的默认对象，并且该 Cue 类不在 AlwaysLoadedCues 中
	{
		PreloadedCues.Add(LoadedGameplayCueClass); // 添加到 PreloadedCues 集合
		TSet<FObjectKey>& ReferencerSet = PreloadedCueReferencers.FindOrAdd(LoadedGameplayCueClass); // 在 PreloadedCueReferencers Map 中查找或添加该 CueClass 对应的引用者集合
		ReferencerSet.Add(OwningObject); // 将所有者对象添加到引用者集合
	}
}

void ULyraGameplayCueManager::HandlePostLoadMap(UWorld* NewWorld) // HandlePostLoadMap 函数的实现
{
	if (RuntimeGameplayCueObjectLibrary.CueSet) // 检查 RuntimeGameplayCueObjectLibrary 的 CueSet 是否存在
	{
		for (UClass* CueClass : AlwaysLoadedCues) // 遍历 AlwaysLoadedCues 集合
		{
			RuntimeGameplayCueObjectLibrary.CueSet->RemoveLoadedClass(CueClass); // 从 CueSet 中移除已加载的 Cue 类（可能用于清理或重置状态）
		}

		for (UClass* CueClass : PreloadedCues) // 遍历 PreloadedCues 集合
		{
			RuntimeGameplayCueObjectLibrary.CueSet->RemoveLoadedClass(CueClass); // 从 CueSet 中移除已加载的 Cue 类
		}
	}

	for (auto CueIt = PreloadedCues.CreateIterator(); CueIt; ++CueIt) // 使用迭代器遍历 PreloadedCues 集合
	{
		TSet<FObjectKey>& ReferencerSet = PreloadedCueReferencers.FindChecked(*CueIt); // 查找该 CueClass 对应的引用者集合，如果找不到则断言失败
		for (auto RefIt = ReferencerSet.CreateIterator(); RefIt; ++RefIt) // 使用迭代器遍历引用者集合
		{
			if (!RefIt->ResolveObjectPtr()) // 检查引用者对象是否仍然有效（通过 FObjectKey 解析）
			{
				RefIt.RemoveCurrent(); // 如果无效，则从引用者集合中移除当前项
			}
		}
		if (ReferencerSet.Num() == 0) // 如果该 CueClass 的引用者集合变为空
		{
			PreloadedCueReferencers.Remove(*CueIt); // 从 PreloadedCueReferencers Map 中移除该 CueClass
			CueIt.RemoveCurrent(); // 从 PreloadedCues 集合中移除当前 CueClass
		}
	}
}

void ULyraGameplayCueManager::UpdateDelayLoadDelegateListeners() // UpdateDelayLoadDelegateListeners 函数的实现
{
	UGameplayTagsManager::Get().OnGameplayTagLoadedDelegate.RemoveAll(this); // 移除所有绑定到 this 对象的 OnGameplayTagLoadedDelegate 委托
	FCoreUObjectDelegates::GetPostGarbageCollect().RemoveAll(this); // 移除所有绑定到 this 对象的 PostGarbageCollect 委托
	FCoreUObjectDelegates::PostLoadMapWithWorld.RemoveAll(this); // 移除所有绑定到 this 对象的 PostLoadMapWithWorld 委托

	switch (LyraGameplayCueManagerCvars::LoadMode) // 根据 LoadMode 的值进行切换
	{
	case ELyraEditorLoadMode::LoadUpfront: // 如果是 LoadUpfront 模式
		return; // 直接返回，不注册委托
	case ELyraEditorLoadMode::PreloadAsCuesAreReferenced_GameOnly: // 如果是 PreloadAsCuesAreReferenced_GameOnly 模式
#if WITH_EDITOR // 如果有编辑器宏定义
		if (GIsEditor) // 如果当前运行在编辑器中
		{
			return; // 直接返回，在编辑器中不注册委托
		}
#endif
		break; // 跳出 switch
	case ELyraEditorLoadMode::PreloadAsCuesAreReferenced: // 如果是 PreloadAsCuesAreReferenced 模式
		break; // 跳出 switch
	}

	UGameplayTagsManager::Get().OnGameplayTagLoadedDelegate.AddUObject(this, &ThisClass::OnGameplayTagLoaded); // 注册 OnGameplayTagLoaded 到 GameplayTagsManager 的 OnGameplayTagLoadedDelegate 委托
	FCoreUObjectDelegates::GetPostGarbageCollect().AddUObject(this, &ThisClass::HandlePostGarbageCollect); // 注册 HandlePostGarbageCollect 到核心 UObject 的 PostGarbageCollect 委托
	FCoreUObjectDelegates::PostLoadMapWithWorld.AddUObject(this, &ThisClass::HandlePostLoadMap); // 注册 HandlePostLoadMap 到核心 UObject 的 PostLoadMapWithWorld 委托
}

bool ULyraGameplayCueManager::ShouldDelayLoadGameplayCues() const // ShouldDelayLoadGameplayCues 函数的实现
{
	const bool bClientDelayLoadGameplayCues = true; // 定义常量布尔值，表示客户端是否延迟加载 GameplayCues，这里设置为 true
	return !IsRunningDedicatedServer() && bClientDelayLoadGameplayCues; // 返回：不是专用服务器 且 客户端延迟加载标志为 true
}

const FPrimaryAssetType UFortAssetManager_GameplayCueRefsType = TEXT("GameplayCueRefs"); // 定义常量 FPrimaryAssetType，表示主资源类型为 "GameplayCueRefs"
const FName UFortAssetManager_GameplayCueRefsName = TEXT("GameplayCueReferences"); // 定义常量 FName，表示主资源名称为 "GameplayCueReferences"
const FName UFortAssetManager_LoadStateClient = FName(TEXT("Client")); // 定义常量 FName，表示加载状态为 "Client"

void ULyraGameplayCueManager::RefreshGameplayCuePrimaryAsset() // RefreshGameplayCuePrimaryAsset 函数的实现
{
	TArray<FSoftObjectPath> CuePaths; // 定义 FSoftObjectPath 数组，用于存储 Cue 的软引用路径
	UGameplayCueSet* RuntimeGameplayCueSet = GetRuntimeCueSet(); // 获取运行时 CueSet
	if (RuntimeGameplayCueSet) // 如果成功获取到 CueSet
	{
		RuntimeGameplayCueSet->GetSoftObjectPaths(CuePaths); // 从 CueSet 中获取所有 Cue 的软引用路径并填充到 CuePaths 数组
	}

	FAssetBundleData BundleData; // 定义 FAssetBundleData，用于存储资源捆绑数据
	BundleData.AddBundleAssetsTruncated(UFortAssetManager_LoadStateClient, CuePaths); // 将 CuePaths 数组中的路径添加到指定 Bundle（"Client"）中，可能会进行截断处理

	FPrimaryAssetId PrimaryAssetId = FPrimaryAssetId(UFortAssetManager_GameplayCueRefsType, UFortAssetManager_GameplayCueRefsName); // 创建主资源 ID，由类型和名称组成
	UAssetManager::Get().AddDynamicAsset(PrimaryAssetId, FSoftObjectPath(), BundleData); // 通过 AssetManager 添加动态资源，指定主资源 ID、空软引用路径（可能表示无具体对象）和捆绑数据
}